using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;

namespace Nate.GUI.Canvas
{
    using Properties;

    public partial class NavMesh
    {
        public class Xml
        {
            public struct Constants
            {
                public const string SchemaFile          = "NavMesh.xsd";
                public const string XmlFile             = "NavMesh.xml";

                public const string RootElement = "NavMesh";


                public const string PolyRoot = "PolyList";
                public const string PolyElement = "Poly";
                public const string PolyElement_EdgeID = "PolyEdge";
                public const string PolyAttribute_ID = "PolyID";

                public const string EdgeRoot = "EdgeList";
                public const string EdgeElement = "Edge";
                public const string EdgeAttribute_ID = "EdgeID";
                public const string EdgeAttribute_VertexA = "VertexA";
                public const string EdgeAttribute_VertexB = "VertexB";
                
                public const string VertexRoot = "VertexList";
                public const string VertexElement = "Vertex";
                public const string VertexAttribute_ID = "VertexID";
                public const string VertexAttribute_X = "X";
                public const string VertexAttribute_Y = "Y";
            }

            public class Exception
            {
                public class MalformedFile : System.Exception { public MalformedFile(string s) : base(s) { } }
            }

            public Xml()
            {
                _GenerateDefaults();

                _schema_set  = new XmlSchemaSet();
                _schema_set.Add(null, Xml.Constants.SchemaFile);
                _schema_set.Compile();
            }

            public Data ReadFromFile(string xml_file)
            {
                _xml_file          = xml_file;
                Data  Data = new Data();

                try
                {
                    XmlDocument doc = new XmlDocument();
                    doc.Load(xml_file);

                    XmlNodeReader node_reader = new XmlNodeReader(doc);
                    XmlReaderSettings settings = new XmlReaderSettings();
                    settings.ValidationType = ValidationType.Schema;

                    settings.Schemas.Add(_schema_set);
                    settings.ValidationEventHandler += new ValidationEventHandler(_ValidationFailed);
                    
                    using (XmlReader reader = XmlReader.Create(node_reader, settings))
                    {
                        Poly poly = null;

                        while (reader.Read())
                        {
                            switch (reader.NodeType)
                            {
                                case XmlNodeType.Element:
                                    switch (reader.Name)
                                    {
                                        case Constants.PolyElement:
                                            {
                                                Poly.ID id = new Poly.ID(int.Parse(reader.GetAttribute(Constants.PolyAttribute_ID)));
                                                poly       = new Poly(id);
                                            }
                                            break;
                                        case Constants.PolyElement_EdgeID:
                                            {
                                                Edge.ID id = new Edge.ID(int.Parse(reader.GetAttribute(Constants.EdgeAttribute_ID)));
                                                poly.Edges.Add(id);
                                            }
                                            break;
                                        case Constants.EdgeElement:
                                            {
                                                Edge.ID   id = new Edge.ID  (int.Parse(reader.GetAttribute(Constants.EdgeAttribute_ID)));
                                                Vertex.ID a = new Vertex.ID(int.Parse(reader.GetAttribute(Constants.EdgeAttribute_VertexA)));
                                                Vertex.ID b = new Vertex.ID(int.Parse(reader.GetAttribute(Constants.EdgeAttribute_VertexB)));

                                                Edge edge    = new Edge(id, a, b);
                                                Data.Add(edge);
                                            }
                                            break;
                                        case Constants.VertexElement:
                                            {
                                                Vertex.ID id = new Vertex.ID(int.Parse(reader.GetAttribute(Constants.VertexAttribute_ID)));
                                                float x = float.Parse(reader.GetAttribute(Constants.VertexAttribute_X));
                                                float y = float.Parse(reader.GetAttribute(Constants.VertexAttribute_Y));

                                                Vertex vertex = new Vertex(id, new PointF(x, y));
                                                Data.Add(vertex);
                                            }
                                            break;
                                    }
                                    break;

                                case XmlNodeType.EndElement:
                                    switch (reader.Name)
                                    {
                                        case Constants.PolyElement:
                                            if (null != poly)
                                            {
                                                Data.Add(poly);
                                                poly = null;
                                            }
                                            break;
                                    }
                                    break;
                            }
                        }
                    }
                }
                finally
                {
                    _xml_file = null;
                }

                return Data;
            }

            public void WriteToFile(string xml_file, NavMesh.Data Data)
            {
                using (FileStream stream = new FileStream(xml_file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Encoding = Encoding.ASCII;
                    settings.Indent = true;

                    using (XmlWriter writer = XmlWriter.Create(stream, settings))
                    {
                        writer.WriteStartDocument();
                        writer.WriteStartElement(Constants.RootElement);

                        // Verts

                        writer.WriteStartElement(Constants.VertexRoot);
                        foreach (Vertex v in Data.Vertices)
                        {
                            writer.WriteStartElement(Constants.VertexElement);
                            writer.WriteAttributeString(Constants.VertexAttribute_ID, v.Id.ToString());
                            writer.WriteAttributeString(Constants.VertexAttribute_X, v.Point.X.ToString());
                            writer.WriteAttributeString(Constants.VertexAttribute_Y, v.Point.Y.ToString());
                            writer.WriteEndElement();
                        }
                        writer.WriteEndElement();

                        // Edges

                        writer.WriteStartElement(Constants.EdgeRoot);
                        foreach (Edge e in Data.Edges)
                        {
                            writer.WriteStartElement(Constants.EdgeElement);
                            writer.WriteAttributeString(Constants.EdgeAttribute_ID, e.Id.ToString());
                            writer.WriteAttributeString(Constants.EdgeAttribute_VertexA, e.A.ToString());
                            writer.WriteAttributeString(Constants.EdgeAttribute_VertexB, e.B.ToString());
                            writer.WriteEndElement();
                        }
                        writer.WriteEndElement();

                        // Polys

                        writer.WriteStartElement(Constants.PolyRoot);
                        foreach (Poly p in Data.Polys)
                        {
                            writer.WriteStartElement(Constants.PolyElement);
                            writer.WriteAttributeString(Constants.PolyAttribute_ID, p.Id.ToString());
                            
                            foreach (Edge.ID edge_id in p.Edges)
                            {
                                writer.WriteStartElement(Constants.PolyElement_EdgeID);
                                writer.WriteAttributeString(Constants.EdgeAttribute_ID, edge_id.ToString());
                                writer.WriteEndElement();
                            }

                            writer.WriteEndElement();
                        }
                        writer.WriteEndElement();

                        writer.WriteEndElement();
                    }
                }
                


            }

            #region Private Methods

            private void _ValidationFailed(object sender, ValidationEventArgs args)
            {
                throw new Exception.MalformedFile("NavMesh file " + _xml_file + " is malformed");
            }

            private void _GenerateDefaults()
            {
                if (!File.Exists(Xml.Constants.XmlFile))
                {
                    using (StreamWriter writer = new StreamWriter(Xml.Constants.XmlFile))
                    {
                        writer.Write(Resources.navmesh);
                    }
                }
                if (!File.Exists(Xml.Constants.SchemaFile))
                {
                    using (StreamWriter writer = new StreamWriter(Xml.Constants.SchemaFile))
                    {
                        writer.Write(Resources.navmesh_xsd);
                    }
                }
            }

            #endregion

            #region Private Data

            private XmlSchemaSet _schema_set;
            private string       _xml_file;

            #endregion
        }
    }
}